/**
 * input、textarea输入控件计数器
 * */
lucky.define(['jquery'],function(exports){
    var MOD_NAME = "maxlength",

        //Input可以提醒的类型
        tipsInputTypes = ['text','password','email','number','search','tel','url'],

        placementPoints = ['bottom','top','left','right','bottom-right','top-right','top-left','bottom-left','centered-right','bottom-right-inside','top-right-inside','top-left-inside','bottom-left-inside'],

        documentBody = $('body'),

        //默认设置
        defaults = {
            showOnReady: false,                     // true 当页面元素初始化完成总是显示
            alwaysShow: false,                      // true 总是显示
            threshold: 10,                           // 10个字符才显示计数器
            warningClass: 'badge badge-success',     // 警告框样式
            limitReachedClass: 'badge badge-danger', // 数量满了以后样式
            separator: ' / ',                        // 分隔符
            preText: '',                             // 前缀提醒文本
            postText: '',                            // 后缀提醒文本
            showMaxLength: true,                    // 显示最大值
            placement: 'bottom',                     // 显示位置
            message: null,                          // 已输入 %charsTyped% /%charsTotal% 字符.
            showCharsTyped: true,                   // 显示输入的字符数，而不是剩余字符数
            validate: false,
            // the indicated chars, will be prevented.
            utf8: false,                            // 按照utf8编码格式对文本计数
            appendToParent: false,
            twoCharLinebreak: true,
            customMaxAttribute: null,               // 自定义属性替代maxlength
            allowOverMax: false                     // 允许最大值
        };
    //如果传递的elem为class的话并且存在多个情况，默认只处理第一个
    var MaxLength = function(elem, options){
        this.elem = elem;
        if(elem){
            this.currentInput = $(elem);
            if(options === undefined){
                this.options = this.optionsRenderAfterHandler();
            }else{
                this.options = $.extend(defaults, options);
            }
        }
        return this;
    },
    maxLength = function(elem, options){
        return new MaxLength(elem, options);
    };

    //参数处理 获取页面自定义的元素
    MaxLength.prototype.optionsRenderAfterHandler = function(){
        var currentInput = this.currentInput;
        var settings = {};
        settings.alwaysShow = (function(){
            var alwaysShowValue = currentInput.attr("max-alwaysShow");
            if(alwaysShowValue){
                return true;
            }
            return false;
        }());
        settings.threshold = (function(){
            var thresholdValue = currentInput.attr("max-threshold");
            if(thresholdValue && $.isNumeric(thresholdValue)){
                return parseInt(thresholdValue);
            }
            return defaults.threshold;
        }());
        settings.warningClass = currentInput.attr("max-warningClass") || defaults.warningClass;
        settings.limitReachedClass = currentInput.attr("max-limitReachedClass") || defaults.limitReachedClass;
        settings.separator = currentInput.attr("max-separator") || defaults.separator;
        settings.preText = currentInput.attr("max-preText") || defaults.preText;
        settings.postText = currentInput.attr("max-postText") || defaults.postText;
        settings.showMaxLength = (function(){
            var showMaxLengthValue = currentInput.attr("max-showMaxLength");
            if(showMaxLengthValue === undefined){
                return defaults.showMaxLength;
            }
            if(showMaxLengthValue){
                return true;
            }
            return false;
        }());
        settings.placement = (function(){
            var showPlacementValue = currentInput.attr("max-placement");
            if($.inArray(showPlacementValue,placementPoints) >= 0){
                return showPlacementValue;
            }
            return 'bottom';
        }());
        settings.message = currentInput.attr("max-message") || defaults.message;
        settings.showCharsTyped = (function(){
            var showCharsTypedValue = currentInput.attr("max-showCharsTyped");
            if(showCharsTypedValue === undefined){
                return defaults.showCharsTyped;
            }
            if(showCharsTypedValue){
                return true;
            }
            return false;
        }());
        settings.validate = (function(){
            var validateValue = currentInput.attr("max-validate");
            if(validateValue){
                return true;
            }
            return false;
        }());
        settings.utf8 = (function(){
            var utf8Value = currentInput.attr("max-utf8");
            if(utf8Value){
                return true;
            }
            return false;
        }());
        settings.appendToParent = (function(){
            var appendToParentValue = currentInput.attr("max-appendToParent");
            if(appendToParentValue){
                return true;
            }
            return false;
        }());
        settings.twoCharLinebreak = (function(){
            var twoCharLinebreakValue = currentInput.attr("max-twoCharLinebreak");
            if(twoCharLinebreakValue === undefined){
                return defaults.twoCharLinebreak;
            }
            if(twoCharLinebreakValue){
                return true;
            }
            return false;
        }());
        settings.customMaxAttribute = (function(){
            var customMaxAttributeValue = currentInput.attr("max-customMaxAttribute");
            if(customMaxAttributeValue){
                return customMaxAttributeValue;
            }
            return null;
        }());
        settings.allowOverMax = (function(){
            var allowOverMaxValue = currentInput.attr("max-allowOverMax");
            if(allowOverMaxValue){
                return true;
            }
            return false;
        }());
        return settings;
    };

    //UTF8编码的计数
    MaxLength.prototype.utf8CharByteCount = function(character){
        var c = character.charCodeAt();
        return !c ? 0 : c < 128 ? 1 : c < 2048 ? 2 : 3;
    };

    //返回UTF8编码的字符串长度
    MaxLength.prototype.utf8Length = function(str){
        var that = this;
        return str.split("").map(that.utf8CharByteCount)
            .concat(0)
            .reduce(function(sum, val) { return sum + val; });
    };

    MaxLength.prototype.inputLength = function(input){
        var that = this, options = that.options, text = input.val();

        if (options.twoCharLinebreak) {
            // Count all line breaks as 2 characters
            text = text.replace(/\r(?!\n)|\n(?!\r)/g, '\r\n');
        } else {
            // Remove all double-character (\r\n) linebreaks, so they're counted only once.
            text = text.replace(new RegExp('\r?\n', 'g'), '\n');
        }

        var currentLength = 0;

        if (options.utf8) {
            currentLength = that.utf8Length(text);
        } else {
            currentLength = text.length;
        }
        return currentLength;
    };

    MaxLength.prototype.truncateChars = function(input, maxlength) {
        var that = this, options = that.options, text = input.val();

        if (options.twoCharLinebreak) {
            text = text.replace(/\r(?!\n)|\n(?!\r)/g, '\r\n');

            if (text[text.length - 1] === '\n') {
                maxlength -= text.length % 2;
            }
        }

        if (options.utf8) {
            var indexedSize = text.split("").map(that.utf8CharByteCount);
            for (
                var removedBytes = 0,
                    bytesPastMax = that.utf8Length(text) - maxlength
                ;removedBytes < bytesPastMax
                ;removedBytes += indexedSize.pop()
            );
            maxlength -= (maxlength - indexedSize.length);
        }

        input.val(text.substr(0, maxlength));
    };

    MaxLength.prototype.charsLeftThreshold = function(input, threshold, maxlength){
        var that = this, options = that.options, output = true;
        if (!options.alwaysShow && (maxlength - that.inputLength(input) > threshold)) {
            output = false;
        }
        return output;
    };

    MaxLength.prototype.remainingChars = function(input, maxlength){
        var length = maxlength - this.inputLength(input);
        return length;
    };

    MaxLength.prototype.showRemaining = function(currentInput, indicator) {
        indicator.css({
            display: 'block'
        });
        currentInput.trigger('maxlength.shown');
    };

    MaxLength.prototype.hideRemaining = function(currentInput, indicator){
        var options = this.options;

        if (options.alwaysShow) {
            return;
        }

        indicator.css({
            display: 'none'
        });
        currentInput.trigger('maxlength.hidden');
    };

    MaxLength.prototype.updateMaxLengthHTML = function(currentInputText, maxLengthThisInput, typedChars) {
        var that = this , output = '';
        if (that.options.message) {
            if (typeof that.options.message === 'function') {
                output = that.options.message(currentInputText, maxLengthThisInput);
            } else {
                output = that.options.message.replace('%charsTyped%', typedChars)
                    .replace('%charsRemaining%', maxLengthThisInput - typedChars)
                    .replace('%charsTotal%', maxLengthThisInput);
            }
        } else {
            if (that.options.preText) {
                output += that.options.preText;
            }
            if (!that.options.showCharsTyped) {
                output += maxLengthThisInput - typedChars;
            }
            else {
                output += typedChars;
            }
            if (that.options.showMaxLength) {
                output += that.options.separator + maxLengthThisInput;
            }
            if (that.options.postText) {
                output += that.options.postText;
            }
        }
        return output;
    };

    MaxLength.prototype.manageRemainingVisibility = function(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator) {
        var that = this;
        if (maxLengthIndicator) {
            maxLengthIndicator.html(that.updateMaxLengthHTML(currentInput.val(), maxLengthCurrentInput, (maxLengthCurrentInput - remaining)));

            if (remaining > 0) {
                if (that.charsLeftThreshold(currentInput, that.options.threshold, maxLengthCurrentInput)) {
                    that.showRemaining(currentInput, maxLengthIndicator.removeClass(that.options.limitReachedClass).addClass(that.options.warningClass));
                } else {
                    that.hideRemaining(currentInput, maxLengthIndicator);
                }
            } else {
                that.showRemaining(currentInput, maxLengthIndicator.removeClass(that.options.warningClass).addClass(that.options.limitReachedClass));
            }
        }

        if (that.options.customMaxAttribute) {
            // class to use for form validation on custom maxlength attribute
            if (remaining < 0) {
                currentInput.addClass('overmax');
            } else {
                currentInput.removeClass('overmax');
            }
        }
    };

    MaxLength.prototype.getPosition = function(currentInput) {
        var el = currentInput[0];
        return $.extend({}, (typeof el.getBoundingClientRect === 'function') ? el.getBoundingClientRect() : {
            width: el.offsetWidth,
            height: el.offsetHeight
        }, currentInput.offset());
    };

    MaxLength.prototype.placeWithCSS = function(placement, maxLengthIndicator) {
        var that = this;
        if (!placement || !maxLengthIndicator){
            return;
        }

        var POSITION_KEYS = [
            'top',
            'bottom',
            'left',
            'right',
            'position'
        ];

        var cssPos = {};

        // filter css properties to position
        $.each(POSITION_KEYS, function (i, key) {
            var val = that.options.placement[key];
            if (typeof val !== 'undefined'){
                cssPos[key] = val;
            }
        });

        maxLengthIndicator.css(cssPos);
        return;
    };

    MaxLength.prototype.place = function(currentInput, maxLengthIndicator){
        var that = this;
        var pos = that.getPosition(currentInput);

        // Supports custom placement handler
        if ($.type(that.options.placement) === 'function'){
            that.options.placement(currentInput, maxLengthIndicator, pos);
            return;
        }

        // Supports custom placement via css positional properties
        if ($.isPlainObject(that.options.placement)){
            that.placeWithCSS(that.options.placement, maxLengthIndicator);
            return;
        }

        var inputOuter = currentInput.outerWidth(),
            outerWidth = maxLengthIndicator.outerWidth(),
            actualWidth = maxLengthIndicator.width(),
            actualHeight = maxLengthIndicator.height();

        // get the right position if the indicator is appended to the input's parent
        if (that.options.appendToParent) {
            pos.top -= currentInput.parent().offset().top;
            pos.left -= currentInput.parent().offset().left;
        }

        switch (that.options.placement) {
            case 'bottom':
                maxLengthIndicator.css({ top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 });
                break;
            case 'top':
                maxLengthIndicator.css({ top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 });
                break;
            case 'left':
                maxLengthIndicator.css({ top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth });
                break;
            case 'right':
                maxLengthIndicator.css({ top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width });
                break;
            case 'bottom-right':
                maxLengthIndicator.css({ top: pos.top + pos.height, left: pos.left + pos.width });
                break;
            case 'top-right':
                maxLengthIndicator.css({ top: pos.top - actualHeight, left: pos.left + inputOuter });
                break;
            case 'top-left':
                maxLengthIndicator.css({ top: pos.top - actualHeight, left: pos.left - outerWidth });
                break;
            case 'bottom-left':
                maxLengthIndicator.css({ top: pos.top + currentInput.outerHeight(), left: pos.left - outerWidth });
                break;
            case 'centered-right':
                maxLengthIndicator.css({ top: pos.top + (actualHeight / 2), left: pos.left + inputOuter - outerWidth - 3 });
                break;

            // Some more options for placements
            case 'bottom-right-inside':
                maxLengthIndicator.css({ top: pos.top + pos.height, left: pos.left + pos.width - outerWidth });
                break;
            case 'top-right-inside':
                maxLengthIndicator.css({ top: pos.top - actualHeight, left: pos.left + inputOuter - outerWidth });
                break;
            case 'top-left-inside':
                maxLengthIndicator.css({ top: pos.top - actualHeight, left: pos.left });
                break;
            case 'bottom-left-inside':
                maxLengthIndicator.css({ top: pos.top + currentInput.outerHeight(), left: pos.left });
                break;
        }
    };

    MaxLength.prototype.isPlacementMutable = function(){
        var that = this;
        return that.options.placement === 'bottom-right-inside' || that.options.placement === 'top-right-inside' || typeof that.options.placement === 'function' || (that.options.message && typeof that.options.message === 'function');
    };

    MaxLength.prototype.getMaxLength = function(currentInput) {
        var that = this;
        var max = currentInput.attr('maxlength') || that.options.customMaxAttribute;

        if (that.options.customMaxAttribute && !that.options.allowOverMax) {
            var custom = currentInput.attr(that.options.customMaxAttribute);
            if (!max || custom < max) {
                max = custom;
            }
        }

        if (!max) {
            max = currentInput.attr('size');
        }
        return max;
    };

    //渲染单个 maxLength(elem,options).render();
    MaxLength.prototype.render = function(){
        var that = this,
            currentInput = that.currentInput,
            maxLengthCurrentInput,
            maxLengthIndicator;

        if(currentInput.attr("maxlength") === undefined){
            return;
        }

        $(window).resize(function(){
            if(maxLengthIndicator){
                that.place(currentInput, maxLengthIndicator);
            }
        });

        function firstInit() {
            var maxlengthContent = that.updateMaxLengthHTML(currentInput.val(), maxLengthCurrentInput, '0');
            maxLengthCurrentInput = that.getMaxLength(currentInput);

            if (!maxLengthIndicator) {
                maxLengthIndicator = $('<span class="bootstrap-maxlength"></span>').css({
                    display: 'none',
                    position: 'absolute',
                    whiteSpace: 'nowrap',
                    zIndex: 1099
                }).html(maxlengthContent);
            }

            // We need to detect resizes if we are dealing with a textarea:
            if (currentInput.is('textarea')) {
                currentInput.data('maxlenghtsizex', currentInput.outerWidth());
                currentInput.data('maxlenghtsizey', currentInput.outerHeight());

                currentInput.mouseup(function () {
                    if (currentInput.outerWidth() !== currentInput.data('maxlenghtsizex') || currentInput.outerHeight() !== currentInput.data('maxlenghtsizey')) {
                        that.place(currentInput, maxLengthIndicator);
                    }

                    currentInput.data('maxlenghtsizex', currentInput.outerWidth());
                    currentInput.data('maxlenghtsizey', currentInput.outerHeight());
                });
            }

            if (that.options.appendToParent) {
                currentInput.parent().append(maxLengthIndicator);
                currentInput.parent().css('position', 'relative');
            } else {
                documentBody.append(maxLengthIndicator);
            }

            var remaining = that.remainingChars(currentInput, that.getMaxLength(currentInput));
            that.manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator);
            that.place(currentInput, maxLengthIndicator);
        }

        if (that.options.showOnReady) {
            currentInput.ready(function () {
                firstInit.call(that);
            });
        } else {
            currentInput.focus(function () {
                firstInit.call(that);
            });
        }

        currentInput.on('maxlength.reposition', function () {
            that.place(currentInput, maxLengthIndicator);
        });


        currentInput.on('destroyed', function () {
            if (maxLengthIndicator) {
                maxLengthIndicator.remove();
            }
        });

        currentInput.on('blur', function () {
            if (maxLengthIndicator && !that.options.showOnReady) {
                maxLengthIndicator.remove();
            }
        });

        currentInput.on('input', function () {
            var maxlength = that.getMaxLength(currentInput),
                remaining = that.remainingChars(currentInput, maxlength),
                output = true;

            if (that.options.validate && remaining < 0) {
                that.truncateChars(currentInput, maxlength);
                output = false;
            } else {
                that.manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator);
            }

            if (that.isPlacementMutable()) {
                that.place(currentInput, maxLengthIndicator);
            }

            return output;
        });

        return that;
    };

    //渲染container下的的input、textarea输入域
    maxLength.render = function(container){
        var fieldElements = $(container).find('input,textarea');
        lucky.each(fieldElements, function () {
            var item = $(this);
            if(item === undefined){
                return;
            }
            var maxLengthAttr = item.attr("maxlength");
            if(maxLengthAttr === undefined){
                return;
            }
            if(item.is("input") && $.inArray(item.attr("type"),tipsInputTypes) === -1){
                return;
            }
            maxLength(item).render();
        });
    };

    exports(MOD_NAME, maxLength);
});